#pragma once
#include "Sem.hpp"
#include "Mutex.hpp"
#include <vector>

using namespace SemModule;
using namespace MutexModule;

static const int gcap = 5;

template <class T>
class RingQueue
{
public:
    RingQueue(int cap = gcap)
        : _rq(cap), _cap(cap), _blank_sem(cap), _p_step(0), _data_sem(0), _c_step(0)
    {
    }

    ~RingQueue()
    {
    }

    void Equeue(const T &in)
    {
        // 生产者
        // 1.申请信号量（空位置）
        //_p_mutex.Lock(); 1
        _blank_sem.P();
        //_p_mutex.Lock(); 2

        //究竟是先申请信号量还是先申请锁？1和2都可以，但是拿看电影来举例，1是排队，排队到了才能买票看电影
        //2是先各自买票（瓜分资源），然后再排队竞争锁，显然2的效率更高，因为没买到票的就没资格来排队

        {
            MutexGuard guard(_p_mutex);
            // 2.生产
            _rq[_p_step] = in;
            // 3.更新下标
            ++_p_step;
            // 4.保持环状特性
            _p_step %= _cap;
        }

        // 5.释放信号量（数据）
        _data_sem.V();
    }

    void Pop(T *out)
    {
        // 消费者
        // 1.申请信号量（数据）
        _data_sem.P();
        {
            MutexGuard guard(_c_mutex);

            // 2.消费
            *out = _rq[_c_step];
            // 3.更新下标
            ++_c_step;
            // 4.保持环状特性
            _c_step %= _cap;
        }
        // 5.释放信号量（空位置）
        _blank_sem.V();
    }

private:
    std::vector<T> _rq;
    int _cap;

    Sem _blank_sem;
    int _p_step;

    Sem _data_sem;
    int _c_step;

    //保证生产者之间，以及消费者之间的互斥
    //而生产者与消费者之间的同步互斥（当两个在同一位置时，_p_step==_c_step,这时候一定为空或为满)，由信号量来保证
    //当两个不在同一位置时，即可同时运行
    Mutex _p_mutex;
    Mutex _c_mutex;
};